import socket
from time import sleep
from os import stat
import gc


class WebServer:
	def __init__(self):
		self._web_server = socket.socket()
		self._connection = None

	def init(self, port: int = 80):
		if (self._web_server is not None):
			self._web_server.close()
			self._web_server = None

		address = socket.getaddrinfo('0.0.0.0', port)[0][-1]

		self._web_server = socket.socket()
		self._web_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self._web_server.bind(address)
		self._web_server.listen(1)
		self._web_server.setblocking(False)

		print("\nServer started, listening on", address)

	def check_client_request_cb(self, timer):
		try:
			if (self._connection is None):
				self._connection, address = self._web_server.accept()

				print("client connected from", address)
			else:
				request = self._connection.recv(1024)

				# 客户端断开连接会发送空字节
				if (request == b''):
					self._close_connection()

					print("connection closed")
				else:
					request_first_line = str(request.split(b'\r\n')[0], "utf-8")

					print("- client request:", request_first_line)

					if (request_first_line.startswith("GET /")):
						request_method, request_file = request_first_line.split(" ")[:2]

						if (len(request_file.split("?")) > 1):
							pass
							# self._respond_to_js(request_method, request_file)
						else:
							self._respond_to_browser(request_method, request_file)

				gc.collect()
		except Exception as ex:
			if (str(ex) == "[Errno 11] EAGAIN"):
				pass
			elif ((str(ex) == "[Errno 104] ECONNRESET") or (str(ex) == "[Errno 9] EBADF")):
				self._close_connection()
				gc.collect()
				self.init()
			else:
				print(ex.args)
				raise ex

	def _respond_to_browser(self, request_method: str, request_file: str):
		print("- request file:", request_file)

		if (request_method == "GET"):
			response = ResponseBuilder()

			if (request_file == "/"):
				filename = "/web/index.html"
			else:
				self._close_connection()
				return

			filesize = stat(filename)[6]

			print("-- file name:", filename)
			print("-- file size:", filesize)

			with (open(filename, "rb", 0)) as file:
				response.set_result_status()
				response.set_content_length(filesize)

				print("- sending response header")
				self._send_response(response.generate_response_header())
				gc.collect()

				total_send_count = 0

				print("- sending response file")

				while True:
					data = file.read(256)
					data_length = len(data)

					if (data_length > 0):
						send_count = self._send_response(data)
						total_send_count += send_count

						if (send_count != data_length):
							#print("--- (%s/%s)" % (send_count, data_length))
							print("--- retry")

							sleep(0.05)
							gc.collect()
							total_send_count += self._send_response(data[send_count:])

						sleep(0.05)
						gc.collect()
					else:
						break

				print("- file send ok")

			sleep(0.05)
			self._close_connection()

	def _send_response(self, response: any):
		result = 0

		if (len(response) > 0):
			if (type(response) is str):
				result = self._connection.send(bytes(response, "utf-8"))
			else:
				result = self._connection.send(response)

		return result

	def _close_connection(self):
		if (self._connection is not None):
			self._connection.close()
			self._connection = None

		print("client connection closed manually\n")



class ResponseBuilder:
	def __init__(self):
		self._body = ""
		self._result_status = ""
		self._content_type = "Content-Type: text/html; charset=UTF-8"
		self._server_name = "Server: ESP8266 Server"
		self._connection = "Connection: keey-alive"
		self._accept_ranges = "Accept-Ranges: bytes"
		self._content_length = ""

	def set_result_status(self, ok: bool = True):
		if (ok):
			self._result_status = "HTTP/1.1 200 OK"
		else:
			self._result_status= "HTTP/1.1 404 File not found"

	def set_content_type(self, content_type: str):
		self._content_type = "Content-Type: " + content_type

	def set_server_name(self, server_name: str):
		self._server_name = server_name

	def set_connection(self, connection):
		self._connection = connection

	def set_content_length(self, content_length):
		self._content_length = "Content-Length:" + str(content_length)

	def set_body_data(self, body_data: str):
		self._body += body_data

	def generate_response_header(self):
		response_header = self._result_status + "\r\n"
		response_header += self._content_type + "\r\n"
		response_header += self._server_name + "\r\n"
		response_header += self._accept_ranges + "\r\n"
		response_header += self._connection + "\r\n"
		response_header += self._content_length + "\r\n"
		response_header += "\r\n"

		return response_header
